﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Threading;
using System.Xml;
using System.ComponentModel;

/**
 * @file Scene
 * 
 * @author LeonXie
 * */

namespace Catsland.Core {
    /**
     * @brief scene class, containing gameObject list and other essentials
     * */
    public class Scene : Drawable {
        // player of the game, consider to remove it
        // TODO consider to remove it
        public GameObject scenePlayer;
        // name of the scene
        public string _sceneName;
        // XNA resource manager. each scene has one
        public ContentManager _contentManager;

        // the boundary of the scene(in game world)
        public Vector2 _XBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 XBound {
            set {
                _XBound = value;
                UpdateDebugVertex();
            }
            get { return _XBound; }
        }
        public Vector2 _YBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 YBound {
            set {
                _YBound = value;
                UpdateDebugVertex();
            }
            get { return _YBound; }
        }

        public Vector2 _ZBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 ZBound {
            set {
                _ZBound = value;
                UpdateDebugVertex();
            }
            get { return _ZBound; }
        }

        // the angle of view in degree, range [0,90], 0 - side view 90 - top view
        public float _viewAngle;
        [CategoryAttribute("View Angle")]
        public float ViewAngle {
            set { setYAngle(value); }
            get { return _viewAngle; }
        }

        // the list of colliders
        public ColliderList _colliderList;
        // the list of renderable interfaces
        public RenderList _renderList;
        // the list of gameObjects
        public GameObjectList _gameObjectList;
        // the list of prefabs
        public PrefabList _prefabList;

        // the list of renderable interfaces which will be rendered only in debug mode
        public DebugDrawableList _debugDrawableList;
        // the list of models
        public ModelList _modelList;
        // the list of materials
        public MaterialList _materialList;

        // target of camera, consider to remove it
        // TODO remove it
        GameObject _target;

        Game para_game;
        String para_sceneFile;
        Thread _loadThread; // set for async loading

        // the vertex of scene debug box
        VertexPositionColor[] m_vertex;
        VertexBuffer m_vertexBuffer;

        bool para_sync;

        // the cos and sin of view angle
        public float _yCos;
        public float _ySin;

        public Scene() {
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
            }
        }

        /**
         * @brief set the position of the vertexes of debug box
         * */
        public void UpdateDebugVertex() {
            // debug info: scene bound
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                m_vertex[0] = m_vertex[4] = new VertexPositionColor(new Vector3(_XBound.X, _YBound.Y * _yCos, 0.0f), Color.White);
                m_vertex[1] = new VertexPositionColor(new Vector3(_XBound.Y, _YBound.Y * _yCos, 0.0f), Color.White);
                m_vertex[2] = new VertexPositionColor(new Vector3(_XBound.Y, _YBound.X * _yCos, 0.0f), Color.White);
                m_vertex[3] = new VertexPositionColor(new Vector3(_XBound.X, _YBound.X * _yCos, 0.0f), Color.White);
            }
        }

        /**
         * @brief set the view angle of the scene
         * 
         * @param the view angle in degree, range [0, 90], 0 - side view 90 - top view
         * 
         * @result
         * */
        public void setYAngle(float angle) {
            _viewAngle = angle;
            _yCos = (float)Math.Cos(angle * MathHelper.Pi / 180.0f);
            _ySin = (float)Math.Sin(angle * MathHelper.Pi / 180.0f);
        }

        /*
		public void LoadSceneSync(String sceneFile, Game game)
		{
			para_game = game;
			para_sceneFile = sceneFile;
			para_sync = false;
			
			LoadScene();

			para_game = null;
			para_sceneFile = null;
		}
        */
        /*
		public void LoadSceneAsync(String sceneFile, Game game)
		{
			para_game = game;
			para_sceneFile = sceneFile;
			para_sync = true;

			m_loadThread = new Thread(new ThreadStart(this.LoadScene));
			m_loadThread.Start();
		}
        */
        public bool IsLoadFinished() {
            if (_loadThread == null) {
                return true;
            }
            return !_loadThread.IsAlive;
        }


        /**
         * @brief test if a position is in the scene boundary
         * 
         * @param XYposition
         * @param Zposition
         * 
         * @result in scene?
         * */
        public bool IsInBound(Vector2 XYposition, float Zposition) {
            if (XYposition.X < _XBound.Y && XYposition.X > _XBound.X
                && XYposition.Y < _YBound.Y && XYposition.Y > _YBound.X
                && Zposition < _ZBound.Y && Zposition > _ZBound.X) {
                return true;
            }
            else {
                return false;
            }
        }

        
        /**
         * @brief get the nearest point in the scene, return original position if in scene
         * 
         * @param XYPosition
         * @param ZPosition
         * 
         * @result the nearest position in the scene
         * */
        public Vector3 GetInBoundPosition(Vector2 XYPosition, float ZPosition) {
            Vector3 result = new Vector3(XYPosition, ZPosition);

            // x
            if (result.X > _XBound.Y) {
                result.X = _XBound.Y;
            }
            else if (result.X < _XBound.X) {
                result.X = _XBound.X;
            }

            // y
            if (result.Y > _YBound.Y) {
                result.Y = _YBound.Y;
            }
            else if (result.Y < _YBound.X) {
                result.Y = _YBound.X;
            }

            // height
            if (result.Z > _ZBound.Y) {
                result.Z = _ZBound.Y;
            }
            else if (result.Z < _ZBound.X) {
                result.Z = _ZBound.X;
            }

            return result;
        }


        /**
         * @brief set this scene to current active scene
         * 
         * we could have more than one scene in memory at a time, but only one active scene 
         * which is being updated and rendered
         * */
        public void ActivateScene() {
            Mgr<MaterialList>.Singleton = _materialList;
            Mgr<ModelList>.Singleton = _modelList;

            Mgr<ColliderList>.Singleton = _colliderList;
            Mgr<RenderList>.Singleton = _renderList;
            Mgr<GameObjectList>.Singleton = _gameObjectList;
            Mgr<Camera>.Singleton.m_target = _target;
            Mgr<DebugDrawableList>.Singleton = _debugDrawableList;

            Mgr<Scene>.Singleton = this;

            para_game = null;
            para_sceneFile = null;
            para_sync = false;
            _loadThread = null;
        }

        
        /**
         * @brief release the scene
         * */
        public void Unload() {
            // release all dynamic list
            Mgr<ColliderList>.Singleton.ReleaseAll();
            Mgr<RenderList>.Singleton.ReleaseAll();
            Mgr<GameObjectList>.Singleton.ReleaseAll();

            // release all resource list
            Mgr<ModelList>.Singleton.ReleaseAll();
            Mgr<MaterialList>.Singleton.ReleaseAll();

            _contentManager.Unload();

            Mgr<ColliderList>.Singleton = null;
            Mgr<RenderList>.Singleton = null;
            Mgr<GameObjectList>.Singleton = null;
            Mgr<ModelList>.Singleton = null;
            Mgr<MaterialList>.Singleton = null;
        }


        /**
         * @brief draw the debug box of the scene
         * 
         * @param timeLastFrame the time interval
         * */
        public void Draw(int timeLastFrame) {
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                UpdateDebugVertex();

                BasicEffect effect = Mgr<DebugTools>.Singleton.DrawEffect;
                effect.View = Mgr<Camera>.Singleton.m_view;
                effect.Projection = Mgr<Camera>.Singleton.m_projection;
                effect.VertexColorEnabled = false;
                effect.World = Matrix.Identity;
                effect.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);

                Mgr<GraphicsDevice>.Singleton.SetVertexBuffer(m_vertexBuffer);
                foreach (EffectPass pass in effect.CurrentTechnique.Passes) {
                    pass.Apply();
                    Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineStrip,
                        m_vertex,
                        0,
                        4);
                }
            }
        }

        public bool SaveToNode(XmlNode node, XmlDocument doc) {
            XmlElement scene = doc.CreateElement("Scene");
            node.AppendChild(scene);

            // scene
            XmlElement basic = doc.CreateElement("SceneBasic");
            scene.AppendChild(basic);
            basic.SetAttribute("viewAngle", "" + _viewAngle);
            basic.SetAttribute("playerGameObjectName", scenePlayer.Name);

            // Bound
            XmlElement XBound = doc.CreateElement("XBound");
            basic.AppendChild(XBound);
            XBound.SetAttribute("min", "" + _XBound.X);
            XBound.SetAttribute("max", "" + _XBound.Y);

            XmlElement YBound = doc.CreateElement("YBound");
            basic.AppendChild(YBound);
            YBound.SetAttribute("min", "" + _YBound.X);
            YBound.SetAttribute("max", "" + _YBound.Y);

            XmlElement ZBound = doc.CreateElement("ZBound");
            basic.AppendChild(ZBound);
            ZBound.SetAttribute("min", "" + _ZBound.X);
            ZBound.SetAttribute("max", "" + _ZBound.Y);

            // materials
            _materialList.SaveToNode(scene, doc);

            // models
            _modelList.SaveToNode(scene, doc);

            // prefabs
            _prefabList.SaveToNode(scene, doc);

            // gameObjects
            _gameObjectList.SaveToNode(scene, doc);
            return true;
        }

        /**
         * @brief create an empty scene
         * 
         * @param game the game engine
         * 
         * @result an empty scene
         * */
        public static Scene EmptyScene(Game game) {
            Scene newScene = new Scene();
            newScene.para_game = game;
            // basic
            newScene.setYAngle(45.0f);
            newScene._XBound = new Vector2(-4.0f, 4.0f);
            newScene._YBound = new Vector2(-0.2f, 0.2f);
            newScene._ZBound = new Vector2(0.0f, 4.0f);

            // Create Scene Content
            newScene._contentManager = new ContentManager(newScene.para_game.Services);
            newScene._contentManager.RootDirectory = "Content";

            // working list
            newScene._renderList = new RenderList();
            newScene._colliderList = new ColliderList();

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                newScene._debugDrawableList = new DebugDrawableList();
                newScene._debugDrawableList.AddItem(newScene);
            }

            // emtpy list
            newScene._materialList = new MaterialList();
            newScene._modelList = new ModelList();
            newScene._gameObjectList = new GameObjectList();
            newScene._prefabList = new PrefabList();

            newScene.para_game = null;
            return newScene;
        }

        /**
         * @brief create a scene from an XML file
         * 
         * @param node the XML node
         * @param game the game engine
         * 
         * @result scene
         * */
        public static Scene LoadFromNode(XmlNode node, Game game) // node is the current node
        {
            Scene newScene = new Scene();
            newScene.para_game = game;

            // Create Scene Content
            newScene._contentManager = new ContentManager(newScene.para_game.Services);
            newScene._contentManager.RootDirectory = "Content";

            // working list
            newScene._renderList = new RenderList();
            newScene._colliderList = new ColliderList();

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                newScene._debugDrawableList = new DebugDrawableList();
                newScene._debugDrawableList.AddItem(newScene);
            }

            // init scene basi list
            // load and construct scene here
            XmlNode basic = node.SelectSingleNode("SceneBasic");
            XmlElement sceneBasic = (XmlElement)basic;

            newScene.setYAngle(float.Parse(sceneBasic.GetAttribute("viewAngle")));
            String playerGameObjectName = sceneBasic.GetAttribute("playerGameObjectName");

            XmlElement XBound = (XmlElement)sceneBasic.SelectSingleNode("XBound");
            newScene._XBound = new Vector2(float.Parse(XBound.GetAttribute("min")),
                                        float.Parse(XBound.GetAttribute("max")));

            XmlElement YBound = (XmlElement)sceneBasic.SelectSingleNode("YBound");
            newScene._YBound = new Vector2(float.Parse(YBound.GetAttribute("min")),
                                        float.Parse(YBound.GetAttribute("max")));

            XmlElement ZBound = (XmlElement)sceneBasic.SelectSingleNode("ZBound");
            newScene._ZBound = new Vector2(float.Parse(ZBound.GetAttribute("min")),
                                        float.Parse(ZBound.GetAttribute("max")));
            // materials
            // release old material list?
            XmlNode materials = node.SelectSingleNode("Materials");
            newScene._materialList = MaterialList.LoadFromNode(materials, newScene);

            // models
            XmlNode models = node.SelectSingleNode("Models");
            newScene._modelList = ModelList.LoadFromNode(models, newScene);

            // PREFAB LIST
            XmlNode prefabs = node.SelectSingleNode("Prefabs");
            newScene._prefabList = PrefabList.LoadFromNode(prefabs, newScene);

            // gameObjects
            XmlNode gameObjects = node.SelectSingleNode("GameObjects");
            newScene._gameObjectList = GameObjectList.LoadFromNode(gameObjects, newScene);

            // set Player
            foreach (GameObject gameObject in newScene._gameObjectList.GetAddList()) {
                if (gameObject.Name == playerGameObjectName) {
                    newScene.scenePlayer = gameObject;
                    newScene._target = gameObject;
                    break;
                }
            }

            newScene.para_game = null;
            PostLoadScene(newScene);
            return newScene;
        }

        /**
         * @brief build a scene with code, only for test use
         * 
         * @param the scene object
         * 
         * @result success?
         * */
        public static bool PostLoadScene(Scene scene) {
            return true;
        }

        /**
         * @brief initialize debug box vertex buffer
         * */
        public void InitializeScene() {
            // Scene
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                m_vertex = new VertexPositionColor[5];
                UpdateDebugVertex();
                m_vertexBuffer = new VertexBuffer(Mgr<GraphicsDevice>.Singleton,
                    typeof(VertexPositionColor),
                    5,
                    BufferUsage.None);
                m_vertexBuffer.SetData(m_vertex);
            }
        }

        public float GetDepth() {
            return 0;
        }

        public int CompareTo(object obj) {
            return 1;
        }
    }
}
